using UnityEngine;
using System;

namespace DanielSig.Geometry
{
	public struct LocationDirection : I64Bit
	{
		public const float X_RANGE = 200000F;
		public const float Z_RANGE = 200000F;
		
		public static LocationDirection ZERO
		{
			get
			{
				return new LocationDirection(DEFAULT);
			}
		}
		
		#region constructors
		public LocationDirection(ulong rawData)
		{
			_data = rawData;
		}
		public LocationDirection(Vector3 location)
		{
			location.x = (location.x + X_CENTER).ClampBetween(0F, X_RANGE);
			location.z = (location.z + Z_CENTER).ClampBetween(0F, Z_RANGE);
			_data = (((ulong)(location.x * UNIT_X)) << X_BIT_LOCATION)
				  | (((ulong)(location.z * UNIT_Z)) << Z_BIT_LOCATION)
				  | DEFAULT_DIRECTION;
		}
		public LocationDirection(Vector3 location, Quaternion direction)
		{
			location.x = (location.x + X_CENTER).ClampBetween(0F, X_RANGE);
			location.z = (location.z + Z_CENTER).ClampBetween(0F, Z_RANGE);
			Vector3 euler = direction.eulerAngles;
			euler.y = (euler.y + 180F).RepeatBetween(-180F, 180F);
			euler.x = (euler.x + 90F).RepeatBetween(-90F, 90F);
			_data = (((ulong)(location.x * UNIT_X)) << X_BIT_LOCATION)
				  | (((ulong)(location.z * UNIT_Z)) << Z_BIT_LOCATION)
				  | (((ulong)(euler.y * UNIT_ROTATION)) << ROTATION_BIT_LOCATION)
				  | (((ulong)(euler.x * UNIT_TILT)) << TILT_BIT_LOCATION);
		}
		public LocationDirection(float x, float z)
		{
			x = (x + X_CENTER).ClampBetween(0F, X_RANGE);
			z = (z + Z_CENTER).ClampBetween(0F, Z_RANGE);
			_data = (((ulong)(x * UNIT_X)) << X_BIT_LOCATION)
				  | (((ulong)(z * UNIT_Z)) << Z_BIT_LOCATION)
				  | DEFAULT_DIRECTION;
			
		}
		public LocationDirection(float x, float z, Quaternion direction)
		{
			x = (x + X_CENTER).ClampBetween(0F, X_RANGE);
			z = (z + Z_CENTER).ClampBetween(0F, Z_RANGE);
			Vector3 euler = direction.eulerAngles;
			euler.y = (euler.y + 180F).RepeatBetween(-180F, 180F);
			euler.x = (euler.x + 90F).RepeatBetween(-90F, 90F);
			_data = (((ulong)(x * UNIT_X)) << X_BIT_LOCATION)
				  | (((ulong)(z * UNIT_Z)) << Z_BIT_LOCATION)
				  | (((ulong)(euler.y * UNIT_ROTATION)) << ROTATION_BIT_LOCATION)
				  | (((ulong)(euler.x * UNIT_TILT)) << TILT_BIT_LOCATION);
		}
		public LocationDirection(float x, float z, float rotation, float tilt)
		{
			x = (x + X_CENTER).ClampBetween(0F, X_RANGE);
			z = (z + Z_CENTER).ClampBetween(0F, Z_RANGE);
			rotation = (rotation + 180F).RepeatBetween(-180F, 180F);
			tilt = (tilt + 90F).RepeatBetween(-90F, 90F);
			_data = (((ulong)(x * UNIT_X)) << X_BIT_LOCATION)
				  | (((ulong)(z * UNIT_Z)) << Z_BIT_LOCATION)
				  | (((ulong)(rotation * UNIT_ROTATION)) << ROTATION_BIT_LOCATION)
				  | (((ulong)(tilt * UNIT_TILT)) << TILT_BIT_LOCATION);
				
		}
		#endregion
		#region getters
		public Vector3 location
		{
			get
			{
				return new Vector3(x, 0F, z);
			}
			set
			{
				x = value.x;
				z = value.z;
			}
		}
		public Quaternion direction
		{
			get
			{
				return Quaternion.Euler(tilt, rotation, 0F);
			}
			set
			{
				Vector3 euler = value.eulerAngles;
				rotation = euler.y;
				tilt = euler.x;
			}
		}
		public float x
		{
			get
			{
				return ((_data >> X_BIT_LOCATION) & X_RESOLUTION) * INV_UNIT_X - X_CENTER;
			}
			set
			{
				value = (value + X_CENTER).ClampBetween(0F, X_RANGE);
				_data = (_data & NO_X) | (((ulong)(value * UNIT_X)) << X_BIT_LOCATION);
			}
		}
		public float z
		{
			get
			{
				return ((_data >> Z_BIT_LOCATION) & Z_RESOLUTION) * INV_UNIT_Z - Z_CENTER;
			}
			set
			{
				value = (value + Z_CENTER).ClampBetween(0F, Z_RANGE);
				_data = (_data & NO_Z) | (((ulong)(value * UNIT_Z)) << Z_BIT_LOCATION);
			}
		}
		public float rotation
		{
			get
			{
				return ((_data >> ROTATION_BIT_LOCATION) & ROTATION_RESOLUTION) * INV_UNIT_ROTATION - 180F;
			}
			set
			{
				value = (value + 180F).RepeatBetween(-180F, 180F);
				_data = (_data & NO_ROTATION) | (((ulong)(value * UNIT_ROTATION)) << ROTATION_BIT_LOCATION);
			}
		}
		public float tilt
		{
			get
			{
				return ((_data >> TILT_BIT_LOCATION) & TILT_RESOLUTION) * INV_UNIT_TILT - 90F;
			}
			set
			{
				value = (value + 90F).RepeatBetween(-90F, 90F);
				_data = (_data & NO_TILT) | (((ulong)(value * UNIT_TILT)) << TILT_BIT_LOCATION);
			}
		}
		public ulong value
		{
			get
			{
				return _data;
			}
			set
			{
				_data = value;
			}
		}
		public ulong rawData
		{
			get
			{
				return _data;
			}
			set
			{
				_data = value;
			}
		}
		#endregion
		
		private ulong _data;
		
		#region constants
		private const int X_BITS = 21;
		private const int Z_BITS = 21;
		private const int ROTATION_BITS = 12;
		private const int TILT_BITS = 10;
		
		private const int TILT_BIT_LOCATION = 0;
		private const int ROTATION_BIT_LOCATION = TILT_BITS;
		private const int Z_BIT_LOCATION = ROTATION_BIT_LOCATION + ROTATION_BITS;
		private const int X_BIT_LOCATION = Z_BIT_LOCATION + Z_BITS;
		
		private const ulong X_RESOLUTION = (1 << X_BITS) - 1;
		private const ulong Z_RESOLUTION = (1 << Z_BITS) - 1;
		private const ulong ROTATION_RESOLUTION = (1 << ROTATION_BITS) - 1;
		private const ulong TILT_RESOLUTION = (1 << TILT_BITS) - 1;
		
		private const ulong X_MASK = X_RESOLUTION << X_BIT_LOCATION;
		private const ulong Z_MASK = Z_RESOLUTION << Z_BIT_LOCATION;
		private const ulong ROTATION_MASK = ROTATION_RESOLUTION << ROTATION_BIT_LOCATION;
		private const ulong TILT_MASK = TILT_RESOLUTION << TILT_BIT_LOCATION;
		
		private const ulong NO_X = ~X_MASK;
		private const ulong NO_Z = ~Z_MASK;
		private const ulong NO_ROTATION = ~ROTATION_MASK;
		private const ulong NO_TILT = ~TILT_MASK;
		
		private const float UNIT_X = X_RESOLUTION / X_RANGE;
		private const float UNIT_Z = Z_RESOLUTION / Z_RANGE;
		private const float UNIT_ROTATION = ROTATION_RESOLUTION / 360F;
		private const float UNIT_TILT = ROTATION_RESOLUTION / 180F;
				
		private const float INV_UNIT_X = X_RANGE / X_RESOLUTION;
		private const float INV_UNIT_Z = Z_RANGE / Z_RESOLUTION;
		private const float INV_UNIT_ROTATION = 360F / ROTATION_RESOLUTION;
		private const float INV_UNIT_TILT = 180F / ROTATION_RESOLUTION;
		
		private const ulong DEFAULT_X = ((ulong)(X_CENTER * UNIT_X)) << X_BIT_LOCATION;
		private const ulong DEFAULT_Z = ((ulong)(Z_CENTER * UNIT_Z)) << Z_BIT_LOCATION;
		private const ulong DEFAULT_ROTATION = ((ulong)(180F * UNIT_ROTATION)) << ROTATION_BIT_LOCATION;
		private const ulong DEFAULT_TILT = ((ulong)(90F * UNIT_TILT)) << TILT_BIT_LOCATION;
		
		private const ulong DEFAULT_LOCATION = DEFAULT_X | DEFAULT_Z;
		private const ulong DEFAULT_DIRECTION = DEFAULT_ROTATION | DEFAULT_TILT;
		private const ulong DEFAULT = DEFAULT_LOCATION | DEFAULT_DIRECTION;
		
		private const float X_CENTER = X_RANGE / 2F;
		private const float Z_CENTER = Z_RANGE / 2F;
		
		#endregion
	}
}

